package com.megalith.service.impl;

import lombok.SneakyThrows;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;

@Service
public class ProcessDiagramService {

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private ProcessEngine processEngine;

    /**
     * 生成流程图PNG（Base64编码）
     */
    @SneakyThrows
    public String generateProcessDiagramPng(String processInstanceId) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        List<String> activeActivityIds = getActiveActivityIds(processInstanceId);
        BpmnModel bpmnModel = getBpmnModel(processInstanceId);
        ProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();


        InputStream inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", activeActivityIds, null, getActivityFontName(), getLabelFontName(), getAnnotationFontName(), null, 1.0, true);


        return inputStreamToBase64(inputStream);
    }

    public static String inputStreamToBase64(InputStream inputStream) throws IOException {
        // ByteArrayOutputStream用于读取InputStream中的数据
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int nRead;
        byte[] data = new byte[1024]; // 缓冲区大小可以根据需要调整
        while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
        buffer.flush(); // 清空缓冲区到输出流中，但不关闭输出流
        byte[] byteArray = buffer.toByteArray(); // 将数据转换成字节数组
        buffer.close(); // 关闭流，释放资源
        inputStream.close(); // 关闭输入流，释放资源
        return Base64.getEncoder().encodeToString(byteArray); // 将字节数组编码为Base64字符串
    }

    /**
     * 生成流程图SVG
     */
    public String generateProcessDiagramSvg(String processInstanceId) throws IOException {
        List<String> activeActivityIds = getActiveActivityIds(processInstanceId);
        BpmnModel bpmnModel = getBpmnModel(processInstanceId);

        ProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        try (InputStream svgStream = diagramGenerator.generateDiagram(
                bpmnModel,
                "svg",
                activeActivityIds,
                new ArrayList<>(),
                getActivityFontName(),
                getLabelFontName(),
                getAnnotationFontName(),
                null,
                1.0,
                true
        )) {
            return new String(readAllBytes(svgStream));
        }
    }

    /**
     * 获取流程定义XML
     */
    public String getProcessDefinitionXml(String processInstanceId) throws IOException {
        ProcessDefinition processDefinition = getProcessDefinition(processInstanceId);
        try (InputStream xmlStream = repositoryService.getResourceAsStream(
                processDefinition.getDeploymentId(),
                processDefinition.getResourceName()
        )) {
            return new String(readAllBytes(xmlStream));
        }
    }

    private BpmnModel getBpmnModel(String processInstanceId) {
        ProcessDefinition processDefinition = getProcessDefinition(processInstanceId);
        return repositoryService.getBpmnModel(processDefinition.getId());
    }

    private ProcessDefinition getProcessDefinition(String processInstanceId) {
        // 先尝试获取运行中的流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();

        if (processInstance != null) {
            return repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());
        }

        // 如果流程已结束，从历史中获取
        HistoricProcessInstance historicInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();

        if (historicInstance == null) {
            throw new RuntimeException("Process instance not found: " + processInstanceId);
        }

        return repositoryService.getProcessDefinition(historicInstance.getProcessDefinitionId());
    }

    private List<String> getActiveActivityIds(String processInstanceId) {
        // 运行中的流程
        if (runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).count() > 0) {
            return runtimeService.getActiveActivityIds(processInstanceId);
        }

        // 已结束的流程：获取最后一个活动节点
        List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceEndTime().desc()
                .listPage(0, 1);

        List<String> activityIds = new ArrayList<>();
        if (!activities.isEmpty()) {
            activityIds.add(activities.get(0).getActivityId());
        }
        return activityIds;
    }

    // 获取字体配置（支持中文）
    private String getActivityFontName() {
        return Optional.of(processEngine.getProcessEngineConfiguration().getActivityFontName()).orElse("Arial");
    }

    private String getLabelFontName() {
        return Optional.of(processEngine.getProcessEngineConfiguration().getLabelFontName()).orElse("Arial");
    }

    private String getAnnotationFontName() {
        return Optional.of(processEngine.getProcessEngineConfiguration().getAnnotationFontName()).orElse("Arial");
    }

    public static byte[] readAllBytes(InputStream inputStream) throws IOException {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            return outputStream.toByteArray();
        }
    }
}